import { ItemStack, Player, world } from "@minecraft/server";
import {
  ascendingSort,
  giveItem,
  randomInteger,
  setEquipmentItem,
  withPercentChance,
} from "../../../utils";

/**
 * Define a gift item.
 */
export class GiftItem {
  constructor(
    /**
     * Identifier of the type of items for the stack. If a namespace is not specified, 'minecraft:' is assumed.
     */
    readonly typeId: string,
    /**
     * Rewards of the gift.
     */
    public reward: RewardType
  ) {
    this.register();
  }
  /**
   * Registry the gift.
   */
  protected register(): void {
    world.afterEvents.itemUse.subscribe((event) => {
      if (event.itemStack.typeId === this.typeId) {
        setEquipmentItem(event.source);
        this.giveReward(event.source);
      }
    });
  }
  /**
   * Give reward to a player.
   * @param player
   */
  giveReward(player: Player): void {
    if (this.reward.items) {
      const ITEMS = this.reward.items;
      ITEMS.forEach((item) => {
        giveItem(player, item);
      });
    }
    if (this.reward.exp) {
      player.addExperience(this.reward.exp);
    }
    if (this.reward.level) {
      player.addExperience(this.reward.level);
    }
    player.playSound("random.levelup");
  }
}

/**
 * A Percent-Driven random reward gift.
 */
export class PercentGiftItem extends GiftItem {
  constructor(
    /**
     * Identifier of the type of items for the stack. If a namespace is not specified, 'minecraft:' is assumed.
     */
    readonly typeId: string,
    /**
     * The probability of give reward when use the gift, should be a percentage (0~1).
     */
    public chance: number,
    /**
     * Rewards of the gift.
     */
    public reward: RewardType
  ) {
    super(typeId, reward);
    this.register();
  }
  /**
   * Give reward to a player.
   * @param player
   */
  giveReward(player: Player): void {
    withPercentChance({
      chance: this.chance,
      event: () => {
        if (this.reward.items) {
          const ITEMS = this.reward.items;
          ITEMS.forEach((item) => {
            giveItem(player, item);
          });
        }
        if (this.reward.exp) {
          player.addExperience(this.reward.exp);
        }
        if (this.reward.level) {
          player.addExperience(this.reward.level);
        }
        player.playSound("random.levelup");
      },
    });
  }
}

/**
 * A Weight-Driven random reward gift.
 */
export class WeightGiftItem {
  constructor(
    /**
     * Identifier of the type of items for the stack. If a namespace is not specified, 'minecraft:' is assumed.
     */
    readonly typeId: string,
    /**
     * Data of the gift, including its reward and weight.
     */
    public data: RewardWeightData[]
  ) {
    this.register();
  }
  /**
   * Registry the gift.
   */
  protected register(): void {
    world.afterEvents.itemUse.subscribe((event) => {
      if (event.itemStack.typeId === this.typeId) {
        setEquipmentItem(event.source);
        this.giveReward(event.source);
      }
    });
  }
  /**
   * Give reward to a player.
   * @param player
   */
  giveReward(player: Player): void {
    let weightArr: number[] = [];
    this.data.forEach((data) => {
      weightArr.push(data.weight);
    });
    const totalWeight = this.data.reduce(
      (accumulator, currentValue) => accumulator + currentValue.weight,
      0
    );
    let random = randomInteger(totalWeight, 1);
    weightArr.push(random);
    ascendingSort(weightArr);
    let index = weightArr.indexOf(random);
    if (this.data[index].reward.items) {
      this.data[index].reward.items.forEach((item) => {
        giveItem(player, item);
      });
    }
    if (this.data[index].reward.exp) {
      player.addExperience(this.data[index].reward.exp);
    }
    if (this.data[index].reward.level) {
      player.addExperience(this.data[index].reward.level);
    }
    player.playSound("random.levelup");
  }
}

/**
 * Types of gift reward.
 */
export interface RewardType {
  /**
   * Player will get these items when the quest is finished.
   */
  items?: ItemStack[];
  /**
   * The specific level will be given to the player.
   */
  level?: number;
  /**
   * The specific point will be given to the player.
   */
  exp?: number;
}

/**
 * Data of the gift, including its reward and weight.
 */
export interface RewardWeightData {
  /**
   * Reward of the gift.
   */
  reward: RewardType;
  /**
   * Weight of giving this reward.
   */
  weight: number;
}
